// https://developer.bitcoin.org/reference/block_chain.html#target-nbits
//
// The target threshold is a 256-bit(32-bytes) unsigned integer
// which a header hash must be equal to or below in order for that header to be a valid part of the block chain.
//
// However, the header field nBits provides only 32 bits of space,
// so the target number uses a less precise format called “compact” which works like a base-256 version of scientific notation.
//
// nBits in big-endian order.
//  The first byte of the compact format as exponent.
//  The remaining 3 bytes of the compact format as mantissa.
//  The mantissa is multiplied by 256^(exponent-3) to get the target threshold.  --- the 3 is bytes in mantissa

use num_bigint::BigUint;
use num_traits::Zero;
use tracing::error;

use crate::block::BlockError;

// Converts the nBits field to a 256-bit integer. A 256-bit integer (BigUint) representing the target difficulty.
pub fn nbits_to_target_threshold(nbits: u32) -> Result<BigUint, BlockError> {
    let nbits_bytes = nbits.to_be_bytes();
    debug_assert!(nbits_bytes.len() == 4, "nbits must be 4 bytes");

    // Extract the exponent and mantissa from nBits
    let exponent = nbits_bytes[0]; // Most significant byte
    let mantissa = &nbits_bytes[1..]; // Remaining 3 bytes
    debug_assert!(mantissa.len() == 3, "Mantissa must be exactly 3 bytes");

    if exponent > 32 {
        // The target threshold is a 256-bit(32-bytes) unsigned integer
        error!("ExponentInNBitsIsTooLarge: {}", exponent);
        return Err(BlockError::ExponentInNBitsIsTooLarge);
    }

    let bytes_num = std::cmp::min(exponent as usize, mantissa.len());
    let mut v: [u8; 32] = [0; 32];
    for (i, it) in mantissa.iter().enumerate().take(bytes_num) {
        let ind: usize = 32 - (exponent as usize - i);
        debug_assert!(ind < 32, "Invalid exponent");
        debug_assert!(i < 3, "Invalid index");
        v[ind] = *it;
    }

    let r = BigUint::from_bytes_be(&v);

    Ok(r)
}

// Converts a BigUint to the nBits compact format.  unsigned int GetCompact() const
pub fn target_threshold_to_nbits(target: &BigUint) -> u32 {
    if target.is_zero() {
        return 0;
    }

    let bytes = target.to_bytes_be();
    let size = bytes.len();
    let mut nbits: [u8; 4] = [0; 4];

    let adjust_size = if size < 3 {
        let zero_byte_num = 3 - size;
        for i in 0..zero_byte_num {
            nbits[i + 1] = 0;
        }

        nbits[(zero_byte_num + 1)..(3 + 1)].copy_from_slice(&bytes[..(3 - zero_byte_num)]);

        3
    } else {
        nbits[1..(3 + 1)].copy_from_slice(&bytes[..3]);

        size
    };

    nbits[0] = adjust_size as u8;
    u32::from_be_bytes(nbits)
}

// reference:
//  CBigNum& SetCompact(unsigned int nCompact)
//  unsigned int GetCompact() const
//
//  https://docs.openssl.org/1.0.2/man3/BN_bn2bin/#description
//  BN_bn2mpi() and BN_mpi2bn() convert BIGNUMs from and to a format
//    that consists of the number's length in bytes represented as a 4-byte big-endian number,
//    and the number itself in big-endian format,
//    where the most significant bit signals a negative number
//    (the representation of numbers with the MSB set is prefixed with null byte).

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_nbits_target() {
        let nbits = 0x1d00ffff;
        let target = nbits_to_target_threshold(nbits).unwrap();
        let calc_nbits = target_threshold_to_nbits(&target);

        // nbits = 1d00ffff, target: 00000000ffff0000000000000000000000000000000000000000000000000000, calc_nbits = 1cffff00
        println!(
            "nbits = {:08x}, target: {:064x}, calc_nbits = {:08x}",
            nbits, target, calc_nbits
        );

        let target_by_calc_nbits = nbits_to_target_threshold(calc_nbits).unwrap();
        assert_eq!(target, target_by_calc_nbits);
    }

    #[test]
    fn test_nbits_to_big_uint_and_back() {
        let nbits_vec = vec![
            0x1d00_ffff, // Bitcoin genesis block
            0x1b04_04cb, // Common compact value
            0x1d00_aaaa, // Random difficulty
            0x1c65_4657, // Litecoin example
            0x0100_3456, // Edge case: very small exponent
            0x1f11_1111, // Large exponent
        ];

        for &nbits in &nbits_vec {
            let target = nbits_to_target_threshold(nbits).unwrap();
            let calc_nbits = target_threshold_to_nbits(&target);
            println!(
                "nbits = {:08x}, target: {:064x}, calc_nbits = {:08x}",
                nbits, target, calc_nbits
            );

            let target_by_calc_nbits = nbits_to_target_threshold(calc_nbits).unwrap();
            assert_eq!(target, target_by_calc_nbits);
        }
    }

    #[test]
    fn test_target_zero() {
        let zero = BigUint::from(0u32);
        let compact = target_threshold_to_nbits(&zero);
        assert_eq!(compact, 0);
    }

    #[test]
    fn test_target_to_nbits_manual() {
        let target = BigUint::from(0x0404cbu32) << (8 * (0x1b - 3));
        let nbits = target_threshold_to_nbits(&target);
        assert_eq!(nbits, 0x1b0404cb);
    }

    struct NbitsTargetPair {
        nbits: u32,
        target: u32,
        target_2_nbits: u32,
    }

    const NBITS_TARGET_PAIRS: [NbitsTargetPair; 5] = [
        NbitsTargetPair {
            nbits: 0x0100_3456,
            target: 0x00,
            target_2_nbits: 0,
        },
        NbitsTargetPair {
            nbits: 0x0112_3456,
            target: 0x12,
            target_2_nbits: 0x0300_0012,
        },
        NbitsTargetPair {
            nbits: 0x0200_8000,
            target: 0x80,
            target_2_nbits: 0x0300_0080,
        },
        NbitsTargetPair {
            nbits: 0x0500_9234,
            target: 0x9234_0000,
            target_2_nbits: 0x0492_3400,
        },
        NbitsTargetPair {
            nbits: 0x0412_3456,
            target: 0x1234_5600,
            target_2_nbits: 0x0412_3456,
        },
    ];

    #[test]
    fn test_nbits_to_target_threshold_base() {
        for pair in NBITS_TARGET_PAIRS {
            let target = nbits_to_target_threshold(pair.nbits).unwrap();
            assert_eq!(target, BigUint::from(pair.target));
        }
    }

    #[test]
    fn test_target_threshold_to_nbits_base() {
        for pair in NBITS_TARGET_PAIRS {
            println!(
                "---- [pairs] nbits = {:08x}, target: {:08x}",
                pair.nbits, pair.target
            );
            let target = BigUint::from(pair.target);
            println!("target: {:064x}", target);
            let calc_nbits = target_threshold_to_nbits(&target);
            println!(
                "calc_nbits = {:08x}, pair.target_2_nbits = {:08x}",
                calc_nbits, pair.target_2_nbits
            );
            assert_eq!(calc_nbits, pair.target_2_nbits);
        }
    }
}

#[cfg(test)]
mod tests_for_learning {
    use num_bigint::BigUint;

    #[test]
    fn test() {
        let nbits = 0x05009234;
        let exponent = (nbits >> 24) as u8; // Most significant byte
        let mantissa: u32 = nbits & 0x00FF_FFFF; // Remaining 3 bytes

        let mantissa_last_3_bytes = &mantissa.to_be_bytes()[1..];
        assert!(
            mantissa_last_3_bytes.len() == 3,
            "Mantissa must be exactly 3 bytes"
        );
        assert_eq!(mantissa_last_3_bytes, &[0, 0x92, 0x34]);
        let bytes_num = std::cmp::min(exponent as usize, mantissa_last_3_bytes.len());
        let mut v: Vec<u8> = vec![0; 32];
        for (i, it) in mantissa_last_3_bytes.iter().enumerate().take(bytes_num) {
            let ind: usize = 32 - (exponent as usize - i);
            debug_assert!(ind < 32, "Invalid exponent");
            v[ind] = *it;
        }

        let b1 = BigUint::from_bytes_be(&v);
        let b2 = BigUint::from(0x92340000u32);
        assert!(b1 == b2);
    }
}
